package io.kimmking.common.zookeeper.dlock;

import io.kimmking.common.zookeeper.properties.ZookeeperProperty;
import io.kimmking.dubbo.common.distributelock.DistributedLock;
import io.kimmking.dubbo.common.distributelock.ZLock;
import io.kimmking.dubbo.common.unit.CommonConstant;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
@ConditionalOnProperty(prefix = "kk.lock", name = "lockerType", havingValue = "zookeeper")
@ConditionalOnBean(ZookeeperProperty.class)
public class ZookeeperDistributedLock implements DistributedLock {
    @Resource
    private CuratorFramework client;

    @Resource
    ZookeeperProperty zookeeperProperty;

    private ZLock getLock(String key,boolean isFair) {

        return isFair ? new ZLock(new InterProcessMutex(client, getPath(key)), this)
                : new ZLock(new NonFairZKDistributedLock(zookeeperProperty), this);
    }

    @Override
    public ZLock lock(String key, long leaseTime, TimeUnit unit, boolean isFair) throws Exception {
        ZLock zLock = this.getLock(key,isFair);

        if (isFair) {
            ((InterProcessMutex) zLock.getLock()).acquire();
        } else {
            ((NonFairZKDistributedLock) zLock.getLock()).acquire(key, leaseTime, unit);
        }
        return zLock;
    }

    /**
     * 此处暂时未实现基于ZK的公平锁和非公平锁,此处借助InterProcessMutex实现的是公平锁，非公平锁需要自己实现(容易导致ZK的羊群效应，所以不推荐)
     * @param key
     * @param waitTime
     * @param leaseTime
     * @param unit
     * @param isFair
     * @return
     * @throws Exception
     */
    @Override
    public ZLock tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, boolean isFair) throws Exception {
        ZLock zLock = this.getLock(key, isFair);
        if (isFair) {
            return ((InterProcessMutex) zLock.getLock()).acquire(waitTime, unit) ? zLock : null;
        } else {
            return ((NonFairZKDistributedLock) zLock.getLock()).tryAcquire(key, waitTime, leaseTime, unit) ? zLock : null;
        }
    }

    @Override
    public void unlock(Object lock) throws Exception {
        Objects.requireNonNull(lock, "release lock failed as lock is null!");
        if (lock instanceof InterProcessMutex) {
            InterProcessMutex ipm = (InterProcessMutex)lock;
            if (ipm.isAcquiredInThisProcess()) {
                ipm.release();
            }
        } else {
            throw new RuntimeException("requires InterProcessMutex type");
        }
    }

    /**
     * 用于释放非公平ZK锁
     * @param lock
     * @param lockKey
     */
    public void unLockNoFairLock(Object lock, String lockKey) {
        Objects.requireNonNull(lock, "release lock failed as lock is null!");

        if(lock instanceof NonFairZKDistributedLock) {
            ((NonFairZKDistributedLock) lock).unAcquire(lockKey);
        } else {
            throw new RuntimeException("requires NonFairZKDistributedLock type");
        }
    }

    private String getPath(String key) {
        return CommonConstant.PATH_SPLIT + CommonConstant.LOCK_KEY_PREFIX + CommonConstant.PATH_SPLIT + key;
    }
}